﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using Microsoft.AspNetCore.Razor.Language.Legacy;
using Microsoft.AspNetCore.Razor.Language.Syntax;
using Microsoft.Extensions.Internal;

namespace Microsoft.AspNetCore.Razor.Language;

public sealed class SourceChange : IEquatable<SourceChange>
{
    public SourceChange(int absoluteIndex, int length, string newText)
    {
        if (absoluteIndex < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(absoluteIndex));
        }

        if (length < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(length));
        }

        if (newText == null)
        {
            throw new ArgumentNullException(nameof(newText));
        }

        Span = new SourceSpan(absoluteIndex, length);
        NewText = newText;
    }

    public SourceChange(SourceSpan span, string newText)
    {
        if (newText == null)
        {
            throw new ArgumentNullException(nameof(newText));
        }

        Span = span;
        NewText = newText;
    }

    public bool IsDelete => Span.Length > 0 && NewText.Length == 0;

    public bool IsInsert => Span.Length == 0 && NewText.Length > 0;

    public bool IsReplace => Span.Length > 0 && NewText.Length > 0;

    public SourceSpan Span { get; }

    public string NewText { get; }

    internal string GetEditedContent(SyntaxNode node)
    {
        if (node == null)
        {
            throw new ArgumentNullException(nameof(node));
        }

        var offset = GetOffset(node);
        return GetEditedContent(node.GetContent(), offset);
    }

    internal string GetEditedContent(string text, int offset)
    {
        if (text == null)
        {
            throw new ArgumentNullException(nameof(text));
        }

        return text.Remove(offset, Span.Length).Insert(offset, NewText);
    }

    internal int GetOffset(SyntaxNode node)
    {
        if (node == null)
        {
            throw new ArgumentNullException(nameof(node));
        }

        var start = Span.AbsoluteIndex;
        var end = Span.AbsoluteIndex + Span.Length;

        if (start < node.Position ||
            start > node.EndPosition ||
            end < node.Position ||
            end > node.EndPosition)
        {
            throw new InvalidOperationException(Resources.FormatInvalidOperation_SpanIsNotChangeOwner(node, this));
        }

        return start - node.Position;
    }

    internal string GetOriginalText(SyntaxNode node)
    {
        if (node == null)
        {
            throw new ArgumentNullException(nameof(node));
        }

        if (node.FullWidth == 0)
        {
            return string.Empty;
        }

        var offset = GetOffset(node);
        return node.GetContent().Substring(offset, Span.Length);
    }

    public bool Equals(SourceChange other)
    {
        return
            other != null &&
            Span.Equals(other.Span) &&
            string.Equals(NewText, other.NewText, StringComparison.Ordinal);
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as SourceChange);
    }

    public override int GetHashCode()
    {
        var hash = HashCodeCombiner.Start();
        hash.Add(Span);
        hash.Add(NewText, StringComparer.Ordinal);
        return hash;
    }

    public override string ToString()
    {
        return Span.ToString() + " : " + NewText;
    }
}
